#include "RenderWindow.h"

namespace Display
{
	RenderWindow::RenderWindow(void)
	{
		swapChain = nullptr;
		device = nullptr;
		context = nullptr;
		rendertargetView = nullptr;
		
		SetBGColor(0.0f,0.0f,0.0f);

	}


	RenderWindow::~RenderWindow(void)
	{
		ReleaseD3D();
		DestroyWindow(windowHandle);
	}

	int RenderWindow::GetHeight()
	{
		return windowHeight;
	}

	int RenderWindow::GetWidth()
	{
		return windowWidth;
	}

	void RenderWindow::SetBGColor(float red,float green,float blue)
	{
		bgColor[0] = red;
		bgColor[1] = green;
		bgColor[2] = blue;
		bgColor[3] = 255.0f;
	}

	RenderWindow * RenderWindow::Create(LPCWSTR windowCaption,int width,int height,int posX,int posY)
	{
		RenderWindow * window = new RenderWindow();
		window->windowWidth = width;
		window->windowHeight = height;

		HINSTANCE instance = GetModuleHandle(nullptr);

		WNDCLASSEX wc;
		ZeroMemory(&wc,sizeof(WNDCLASSEX));


		wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
		wc.lpfnWndProc = WndProc;
		wc.cbClsExtra = 0;
		wc.cbWndExtra = 0;
		wc.hInstance = instance;
		wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
		wc.hIconSm = wc.hIcon;
		wc.hCursor = LoadCursor(NULL, IDC_ARROW);
		wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
		wc.lpszMenuName = NULL;
		wc.lpszClassName = L"Game";
		wc.cbSize = sizeof(WNDCLASSEX);

		RegisterClassEx(&wc);

		window->windowHandle = CreateWindowEx(WS_EX_APPWINDOW, L"Game", windowCaption, 
			WS_OVERLAPPEDWINDOW,
			posX, posY, width, height, NULL, NULL, instance, NULL);

		window->InitD3D();

		ShowWindow(window->windowHandle,SW_SHOW);
		UpdateWindow(window->windowHandle);

		window->closed = false;



		return window;


	}

	void RenderWindow::Destroy(RenderWindow * window)
	{
		if(window != nullptr)
			delete window;
	}

	LRESULT CALLBACK RenderWindow::WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
	{
		switch(umsg)
		{
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;

		default:
			return DefWindowProc(hwnd, umsg, wparam, lparam);

		}
	}

	bool RenderWindow::IsClosed()
	{
		return closed;
	}

	void RenderWindow::Close()
	{
		closed = true;
	}

	void RenderWindow::HandleMessages()
	{
		if(PeekMessage(&msg,nullptr,0,0,PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		if(msg.message == WM_QUIT)
			Close();
	}

	void RenderWindow::InitD3D()
	{
		DXGI_SWAP_CHAIN_DESC swapChainDesc;

		ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));


		swapChainDesc.BufferCount = 1;
		swapChainDesc.BufferDesc.Width = windowWidth;
		swapChainDesc.BufferDesc.Height = windowHeight;
		swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

		swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = 0;

		swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swapChainDesc.SampleDesc.Count = 1;
		swapChainDesc.SampleDesc.Quality = 0;
		swapChainDesc.Windowed = true;
		swapChainDesc.OutputWindow = windowHandle;
		swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

		swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
		swapChainDesc.Flags = 0;

		D3D_FEATURE_LEVEL featurelevels[] = {D3D_FEATURE_LEVEL_11_0};
		int numberOfFeatureLevels = 1;
		D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, featurelevels, numberOfFeatureLevels, 
			D3D11_SDK_VERSION, &swapChainDesc, &swapChain, &device, nullptr, &context);

		ID3D11Texture2D * backBuffer;
		swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBuffer);
		device->CreateRenderTargetView(backBuffer,nullptr,&rendertargetView);
		backBuffer->Release();

		D3D11_TEXTURE2D_DESC depthDesc;
		depthDesc.Width = windowWidth;
		depthDesc.Height = windowHeight;
		depthDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
		depthDesc.SampleDesc.Count = 1;
		depthDesc.SampleDesc.Quality = 0;
		depthDesc.MipLevels = 1;
		depthDesc.ArraySize = 1;
		depthDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
		depthDesc.Usage = D3D11_USAGE_DEFAULT;
		device->CreateTexture2D(&depthDesc,nullptr,&depthBuffer);

		D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
		depthStencilDesc.DepthEnable = true;
		depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
		depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

		depthStencilDesc.StencilEnable = true;
		depthStencilDesc.StencilReadMask = 0xFF;
		depthStencilDesc.StencilWriteMask = 0xFF;

		// Stencil operations if pixel is front-facing.
		depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
		depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
		depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
		depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

		// Stencil operations if pixel is back-facing.
		depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
		depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
		depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
		depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

		device->CreateDepthStencilState(&depthStencilDesc,&depthState);
		context->OMSetDepthStencilState(depthState,1);

		D3D11_DEPTH_STENCIL_VIEW_DESC viewDesc;
		viewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
		viewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
		viewDesc.Texture2D.MipSlice = 0;
		
		device->CreateDepthStencilView(depthBuffer,&viewDesc,&depthView);

		context->OMSetRenderTargets(1,&rendertargetView,depthView);

		D3D11_VIEWPORT port;
		port.Height = (float)windowHeight;
		port.Width = (float)windowWidth;
		port.MaxDepth = 1.0f;
		port.MinDepth = 0.0f;
		port.TopLeftX = 0.0f;
		port.TopLeftY = 0.0f;

		context->RSSetViewports(1,&port);

	}

	void RenderWindow::ReleaseD3D()
	{
		if(depthBuffer != nullptr)
		{
			depthBuffer->Release();
			depthBuffer = nullptr;

		}

		if(depthState != nullptr)
		{
			depthState->Release();
			depthState = nullptr;
		}

		if(depthView != nullptr)
		{
			depthView->Release();
			depthView = nullptr;

		}
		if(rendertargetView != nullptr)
		{
			rendertargetView->Release();
			rendertargetView = nullptr;
		}

		if(device != nullptr)
		{
			device->Release();
			device = nullptr;
		}

		if(swapChain != nullptr)
		{
			swapChain->Release();
			swapChain = nullptr;
		}

	}

	void RenderWindow::Draw()
	{
		BeginDraw();

		//draw scene
		EndDraw();
		
	}

	ID3D11Device * RenderWindow::GetDevice()
	{
		return device;
	}

	ID3D11DeviceContext * RenderWindow::GetContext()
	{
		return context;
	}

	void RenderWindow::BeginDraw()
	{
		context->ClearRenderTargetView(rendertargetView,bgColor);
		context->ClearDepthStencilView(depthView,D3D11_CLEAR_DEPTH,1.0f,0);
	}

	void RenderWindow::EndDraw()
	{
		swapChain->Present(0,0);
	}

}
